package ca.krakenpower.kvoxels;

import java.util.Random;

public class NoiseGen {
	
	private static final Vector3[] UNIT_VECTORS;
	static {
		UNIT_VECTORS = new Vector3[26];
		
		UNIT_VECTORS[0] = Vector3.get(0, 1, 0).normalize();
		UNIT_VECTORS[1] = Vector3.get(0, -1, 0).normalize();
		UNIT_VECTORS[2] = Vector3.get(1, 0, 0).normalize();
		UNIT_VECTORS[3] = Vector3.get(1, 1, 0).normalize();
		UNIT_VECTORS[4] = Vector3.get(1, -1, 0).normalize();
		UNIT_VECTORS[5] = Vector3.get(-1, 0, 0).normalize();
		UNIT_VECTORS[6] = Vector3.get(-1, 1, 0).normalize();
		UNIT_VECTORS[7] = Vector3.get(-1, -1, 0).normalize();
		UNIT_VECTORS[8] = Vector3.get(0, 0, 1).normalize();
		UNIT_VECTORS[9] = Vector3.get(0, 1, 1).normalize();
		UNIT_VECTORS[10] = Vector3.get(0, -1, 1).normalize();
		UNIT_VECTORS[11] = Vector3.get(1, 0, 1).normalize();
		UNIT_VECTORS[12] = Vector3.get(1, 1, 1).normalize();
		UNIT_VECTORS[13] = Vector3.get(1, -1, 1).normalize();
		UNIT_VECTORS[14] = Vector3.get(-1, 0, 1).normalize();
		UNIT_VECTORS[15] = Vector3.get(-1, 1, 1).normalize();
		UNIT_VECTORS[16] = Vector3.get(-1, -1, 1).normalize();
		UNIT_VECTORS[17] = Vector3.get(0, 0, -1).normalize();
		UNIT_VECTORS[18] = Vector3.get(0, 1, -1).normalize();
		UNIT_VECTORS[19] = Vector3.get(0, -1, -1).normalize();
		UNIT_VECTORS[20] = Vector3.get(1, 0, -1).normalize();
		UNIT_VECTORS[21] = Vector3.get(1, 1, -1).normalize();
		UNIT_VECTORS[22] = Vector3.get(1, -1, -1).normalize();
		UNIT_VECTORS[23] = Vector3.get(-1, 0, -1).normalize();
		UNIT_VECTORS[24] = Vector3.get(-1, 1, -1).normalize();
		UNIT_VECTORS[25] = Vector3.get(-1, -1, -1).normalize();
	}
	
	private Vector3[][][] gradientField;
	
	private int size;
	private long seed;
	private Random rng;
	
	public NoiseGen(int size, long seed) {
		
		this.gradientField = new Vector3[size][size][size];
		this.size = size;
		this.seed = seed;
		
		this.rng = new Random(seed);
		
	}
	
	public int getSize() {
		return size;
	}
	
	public long getSeed() {
		return seed;
	}
	
	public double getValue2(Vector3 pos) {
		
		Vector3 g[] = new Vector3[8];
		Vector3 v[] = new Vector3[8];
		
		int x0, y0, z0, x1, y1, z1;
		x0 = (int) Math.floor(pos.x);
		y0 = (int) Math.floor(pos.y);
		z0 = (int) Math.floor(pos.z);
		x1 = (x0 + 1);
		y1 = (y0 + 1);
		z1 = (z0 + 1);
		
		x0 %= size;
		x1 %= size;
		y0 %= size;
		y1 %= size;
		z0 %= size;
		z1 %= size;
		
		g[0b000] = gradientField[z0][y0][x0];
		g[0b001] = gradientField[z0][y0][x1];
		g[0b010] = gradientField[z0][y1][x0];
		g[0b011] = gradientField[z0][y1][x1];
		g[0b100] = gradientField[z1][y0][x0];
		g[0b101] = gradientField[z1][y0][x1];
		g[0b110] = gradientField[z1][y1][x0];
		g[0b111] = gradientField[z1][y1][x1];
		
		v[0b000] = pos.sum(g[0b000].negate());
		v[0b001] = pos.sum(g[0b001].negate());
		v[0b010] = pos.sum(g[0b010].negate());
		v[0b011] = pos.sum(g[0b011].negate());
		v[0b100] = pos.sum(g[0b100].negate());
		v[0b101] = pos.sum(g[0b101].negate());
		v[0b110] = pos.sum(g[0b110].negate());
		v[0b111] = pos.sum(g[0b111].negate());
		
		double[] i = new double[8];
		i[0b000] = g[0b000].dot(v[0b000]);
		i[0b001] = g[0b001].dot(v[0b001]);
		i[0b010] = g[0b010].dot(v[0b010]);
		i[0b011] = g[0b011].dot(v[0b011]);
		i[0b100] = g[0b100].dot(v[0b100]);
		i[0b101] = g[0b101].dot(v[0b101]);
		i[0b110] = g[0b110].dot(v[0b110]);
		i[0b111] = g[0b111].dot(v[0b111]);
		
		double wx, wy, wz;
		wx = ease(pos.x - x0);
		wy = ease(pos.y - y0);
		wz = ease(pos.z - z0);
		
		// a = s + wx(t - s)
		double r00x, r01x, r10x, r11x;
		r00x = i[0b000] + wx * (i[0b001] - i[0b000]);
		r01x = i[0b010] + wx * (i[0b011] - i[0b010]);
		r10x = i[0b100] + wx * (i[0b101] - i[0b100]);
		r11x = i[0b110] + wx * (i[0b111] - i[0b110]);
		
		double r0yx, r1yx;
		r0yx = r00x + wy * (r01x - r00x);
		r1yx = r10x + wy * (r11x - r10x);
		
		double r;
		r = r0yx + wz * (r1yx - r0yx);
		
		return r;
	}
	
	public double getValue(Vector3 pos) {
		
		Vector3 g[] = new Vector3[8];
		Vector3 v[] = new Vector3[8];
		
		int x0, y0, z0, x1, y1, z1;
		x0 = (int) Math.floor(pos.x);
		y0 = (int) Math.floor(pos.y);
		z0 = (int) Math.floor(pos.z);
		x1 = (x0 + 1);
		y1 = (y0 + 1);
		z1 = (z0 + 1);
		
		x0 %= size;
		x1 %= size;
		y0 %= size;
		y1 %= size;
		z0 %= size;
		z1 %= size;
		
		g[0b000] = gradientField[z0][y0][x0];
		g[0b001] = gradientField[z0][y0][x1];
		g[0b010] = gradientField[z0][y1][x0];
		g[0b011] = gradientField[z0][y1][x1];
		g[0b100] = gradientField[z1][y0][x0];
		g[0b101] = gradientField[z1][y0][x1];
		g[0b110] = gradientField[z1][y1][x0];
		g[0b111] = gradientField[z1][y1][x1];
		
		v[0b000] = Vector3.get(pos.x - x0, pos.y - y0, pos.z - z0);
		v[0b001] = Vector3.get(pos.x - x1, pos.y - y0, pos.z - z0);
		v[0b010] = Vector3.get(pos.x - x0, pos.y - y1, pos.z - z0);
		v[0b011] = Vector3.get(pos.x - x1, pos.y - y1, pos.z - z0);
		v[0b100] = Vector3.get(pos.x - x0, pos.y - y0, pos.z - z1);
		v[0b101] = Vector3.get(pos.x - x1, pos.y - y0, pos.z - z1);
		v[0b110] = Vector3.get(pos.x - x0, pos.y - y1, pos.z - z1);
		v[0b111] = Vector3.get(pos.x - x1, pos.y - y1, pos.z - z1);
		
		double[] i = new double[8];
		i[0b000] = g[0b000].dot(v[0b000]);
		i[0b001] = g[0b001].dot(v[0b001]);
		i[0b010] = g[0b010].dot(v[0b010]);
		i[0b011] = g[0b011].dot(v[0b011]);
		i[0b100] = g[0b100].dot(v[0b100]);
		i[0b101] = g[0b101].dot(v[0b101]);
		i[0b110] = g[0b110].dot(v[0b110]);
		i[0b111] = g[0b111].dot(v[0b111]);
		
		double wx, wy, wz;
		wx = ease(pos.x - x0);
		wy = ease(pos.y - y0);
		wz = ease(pos.z - z0);
		
		// a = s + wx(t - s)
		double r00x, r01x, r10x, r11x;
		r00x = i[0b000] + wx * (i[0b001] - i[0b000]);
		r01x = i[0b010] + wx * (i[0b011] - i[0b010]);
		r10x = i[0b100] + wx * (i[0b101] - i[0b100]);
		r11x = i[0b110] + wx * (i[0b111] - i[0b110]);
		
		double r0yx, r1yx;
		r0yx = r00x + wy * (r01x - r00x);
		r1yx = r10x + wy * (r11x - r10x);
		
		double r;
		r = r0yx + wz * (r1yx - r0yx);
		
		return r;
	}
	
	private static double ease(double p) {
		// 3p^2 - 2p^3
		return (3 * Math.pow(p, 2)) - (2 * Math.pow(p, 3));
	}
	
	public void generateNoiseFunction() {
		
		for (Vector3[][] s : gradientField) {
			for (Vector3[] r : s) {
				for (int i = 0; i < size; i++) {
					r[i] = Vector3.get(rng.nextDouble()- 0.5, rng.nextDouble() - 0.5, rng.nextDouble() - 0.5).normalize();
				}
			}
		}
		
	}

}
